#pragma once
#include <atomic>
#include <condition_variable>
#include <functional>
#include <future>
#include <mutex>
#include <queue>
#include <vector>

namespace myFs {
namespace basic {
class ThreadPool {
    using Task = ::std::function<void()>;
    ::std::vector<::std::thread> m_threads;
    ::std::queue<Task> m_taskQue;
    mutable ::std::mutex m_mtx;
    mutable ::std::condition_variable m_cv;
    ::std::atomic<bool> m_hasStop;

   public:
    ThreadPool(size_t numOfThread) { addThreads(numOfThread); }
    ~ThreadPool() {
        for (auto& t : m_threads) {
            if (t.joinable()) t.join();
        }
    }
    void stop() {
        m_hasStop = true;
        m_cv.notify_all();
    }

    template <typename Func, typename... Args>
    auto commit(Func&& func, Args&&... args)
        -> ::std::future<decltype(func(args...))> {
        if (m_hasStop) throw ::std::runtime_error("threadpool has stopped.");
        using RetType = ::std::future<decltype(func(args...))>;
        auto pPackagedTask =
            ::std::make_shared<::std::packaged_task<RetType()>>{::std::bind(
                std::forward<Func>(func), std::forward<Args>(args)...)};
        auto ret = pPackagedTask->get_future();
        {
            ::std::unique_lock<::std::mutex> locker(m_mtx);
            m_taskQue.emplace([pPackagedTask]() { (*pPackagedTask)(); });
        }
        m_cv.notify_one();
        return ret;
    }

    void addThreads(size_t numOfThread = 0) {
        if (numOfThread == 0)
            numOfThread = ::std::thread::hardware_concurrency();
        for (size_t i = 0; i < numOfThread; i++) {
            m_threads.emplace_back([this]() { workThread(); });
        }
    }

   private:
    void workThread() {
        while (true) {
            Task task;
            {
                ::std::unique_lock<::std::mutex> locker(m_mtx);
                if (m_taskQue.empty())
                    m_cv.wait(locker, [this] {
                        return m_hasStop || !m_taskQue.empty();
                    });
                if (m_hasStop && m_taskQue.empty()) return;
                task = std::move(m_taskQue.front());
                m_taskQue.pop();
            }
            task();
        }
    }
};
}  // namespace basic
}  // namespace myFs